package main

import (
	"fmt"
	"sync"
	"time"
)

// 条件变量
/**
	sync.Cond 条件变量
	通过 sync.NewCond(..锁..) 来构建

	// 堵塞
	func (*sync.Cond) Wait()
	// 唤醒单个 goroutine
	func (*sync.Cond) Signal()
	// 唤醒所有 goroutine
	func (*sync.Cond) Broadcast()

	// 例子：
	// 读写锁
	rw := sync.RWMutex
	// 设置条件变量是读写锁
	cond := sync.NewCond(rw)

	通过 sync.NewCond(..锁..) 来构建，会自动的对该条件变量关联的那个锁进行解锁，并且使所在的 goroutine 进行堵塞,
	这个堵塞只会堵塞，使用 当前条件变量锁 的goroutine进行堵塞, 比如这个 goroutine 使用了 rw.Lock() 锁，
	那么执行到这个 rw.Lock() 锁的 goroutine，然后继续执行到 cond.Wait() 全部堵塞,
	其他没有 执行到 rw.Lock() 锁的，并且也没有 执行到 cond.Wait()的  goroutine，不会堵塞！
	cond.Signal() 和 cond.Broadcast() 唤醒，只会唤醒被被相关锁堵塞的 goroutine!

	所以在使用 cond.Wait() 之前，一定要 用设置的条件变量锁，先获得这个锁,
	比如上面的例子：先获得 rm.Lock(),然后执行到 cond.Wait() 堵塞函数时，
	cond.Wait()函数内部会对这个 rm.Lock() 锁自动的进行解锁(rm.UnLock())操作！然后堵塞goroutine


**/

// User 模拟用户
type User struct {
	mail *Mail
}

// Mail 模拟邮箱
type Mail struct {
	mail     bool
	sendCond *sync.Cond
	recvCond *sync.Cond
	lock     sync.RWMutex
	sendName string
	recvName string
}

func NewMail() *Mail {
	m := Mail{}
	m.sendCond = sync.NewCond(&m.lock)
	m.recvCond = sync.NewCond(m.lock.RLocker())
	return &m
}

func (m *Mail) SendMail() {
	// 在使用 m.sendCond.Wait() 之前，先要获得 m.lock.Lock() 锁
	m.lock.Lock()
	time.Sleep(time.Minute)
	if m.mail {
		// m.lock.Wait() 内部会自动对 m.lock.Lock() 执行解锁操作
		// 然后堵塞
		m.sendCond.Wait()
		fmt.Println("已经有邮件了等待接收")
	}
	m.mail = true
	fmt.Println("发送了一封邮件给:", m.recvName)
	// 这里需要解锁， 因为如果没有进入 if 判断
	m.lock.Unlock()
	// 唤醒因为 m.lock.RLocker() 堵塞的 goroutine
	// 因为 m.recvCond 实例化的时候，条件变量的锁是 m.lock.RLocker()
	m.recvCond.Signal() //向接收者发送消息
}

func (m *Mail) RecvMail() {
	m.lock.RLock()

	if !m.mail {
		fmt.Printf("没有邮件，等待邮箱发送邮件\n")
		m.recvCond.Wait()
	}
	m.mail = false
	fmt.Printf("%s收到一封%s发送的邮件\n", m.recvName, m.sendName)
	m.lock.RUnlock()
	m.sendCond.Signal()
}

func main() {
	mail := NewMail()
	lock := make(chan struct{}, 2)
	use1 := User{}
	use1.mail = mail
	use1.mail.sendName = "jacky"
	use2 := User{}
	use2.mail = mail
	use2.mail.recvName = "rose"

	//send mail
	go func() {
		defer func() {
			lock <- struct{}{}
		}()

		for {
			use1.mail.SendMail()
			time.Sleep(time.Second)
		}
	}()

	go func() {
		defer func() {
			lock <- struct{}{}
		}()
		for {
			use2.mail.RecvMail()
			time.Sleep(time.Second)
		}
	}()
	<-lock
	<-lock
}
